// By Tyrghen
// Functions to handle Hash and Arrays.
// In order to support the multi-mission framework, I needed to have apart workspaces for each missions. While you can handle it with arrays, Hashes are more readable.
// But since ArmA doesn't support hashes by Standard, I wrote my own handlers.
#define __DUMP_INDENT__	"    "

/// @FUNCTION
/// @NAME XfHashGet
/// @DESC 
/// @ACCESS Server
/// @MP False
/// @PARAM Array The hash table
/// @PARAM String The name of the key in the hash you want to retrieve
/// @PARAM Variant The default value that you expect out of the method if the key is not found
/// @RETURN The value contained in the hash or the default
/// @REQUIRE Nothing
/// @REMARK
/// @EXAMPLE [_instances, "Reinforcements", []] call XfGetHash
// converted
XfHashGet = {
	private ["_hash_array","_hash_key","_hash_indexer","_index","_hash_values","_result","_default"];
	If (count _this > 2) then {	_default = _this select 2; };
	If (typeName _this == "ARRAY") then {
		if (count _this >= 2) then {
			_hash_array = _this select 0;
			_hash_key = _this select 1;
			if ((typeName _hash_array == "ARRAY") && (typeName _hash_key == "STRING")) then {
				// For safety and easyness, we Upper all keys internally
				_hash_key = toUpper _hash_key;
				// Normally "== 2" is enough since a hash only needs a key indexer and a value array, but in the future we might extend it to more keys for multi dimensional storage
				//  So they 1st dimension keys array is always the first and the values container is always the last one
				if (count _hash_array >= 2) then {
					_hash_indexer = _hash_array select 0;
					_hash_values = _hash_array select (count _hash_array - 1);
					_index = _hash_indexer find _hash_key;
					if (_index >= 0 && _index < count _hash_values) then {
						_result = _hash_values select _index;
					};
				};				
				if ((isNil "_result") && !(IsNil "_default")) then { _result = _default };
			};
		};
	};
	_result;
};

/// @FUNCTION
/// @NAME XfHashSet
/// @DESC Set a value into an hash array.
/// @ACCESS Server
/// @MP False
/// @PARAM Array The hash table
/// @PARAM String The name of the key in the hash you want to set
/// @PARAM Variant The value that you want to set in the hash
/// @RETURN Boolean Succeeded or not
/// @REQUIRE Nothing
/// @REMARK
/// @EXAMPLE _success = [_instances, "Reinforcements", [123,234]] call XfHashSet
// converted
XfHashSet = {
	private ["_hash_array","_hash_key","_hash_indexer","_index","_hash_values","_result","_value"];
	_result = false;
	if (typeName _this == "ARRAY") then {
		if (count _this >= 3) then {
			_hash_array = _this select 0;
			_hash_key = _this select 1;
			_value = _this select 2;
			if ((typeName _hash_array == "ARRAY") && (typeName _hash_key == "STRING")) then {
				// For safety and easyness, we Upper all keys internally
				_hash_key = toUpper _hash_key;
				// Normally "== 2" is enough since a hash only needs a key indexer and a value array, but in the future we might extend it to more keys for multi dimensional storage
				//  So they 1st dimension keys array is always the first and the values container is always the last one
				if (count _hash_array >= 2) then {
					_hash_indexer = _hash_array select 0;
					_hash_values = _hash_array select (count _hash_array - 1);
					If !(_hash_key in _hash_indexer) then {
						_hash_indexer set [count _hash_indexer, _hash_key];
					};
					_index = _hash_indexer find _hash_key;
					_hash_values set[_index, _value];
					_hash_array set [0, _hash_indexer];
					_hash_array set [(count _hash_array - 1),_hash_values];
					_result = true;
				};				
				if ((isNil "_result") && !(IsNil "_default")) then { _result = _default };
			};
		};
	};
	_result;
};

/// @FUNCTION
/// @NAME XfHashDump
/// @DESC Dumps the content of an hash array to the Log file
/// @ACCESS Server
/// @MP False
/// @PARAM Array The hash table
/// @RETURN Nothing
/// @REQUIRE Nothing
/// @REMARK
/// @EXAMPLE [_instances] call XfHashDump
//converted
XfHashDump = {
	private ["_hash_array","_hash_key","_hash_indexer","_index","_hash_values","_value","_indent","_indentval","_i"];
	_result = false;
	if (typeName _this == "ARRAY") then {
		if (count _this >= 1) then {
			_hash_array = _this select 0;
			if (typeName _hash_array == "ARRAY") then {
				if (count _hash_array >= 2) then {
					_hash_indexer = _hash_array select 0;
					_hash_values = _hash_array select (count _hash_array - 1);
					_indent = __DUMP_INDENT__;
					diag_log "";
					diag_log Format["%1DUMP HASH ARRAY: %2",_indent, count _hash_indexer];
					diag_log Format["%1-----------------------",_indent];
					_indent = _indent + __DUMP_INDENT__;
					for "_i" from 0 to (count _hash_indexer - 1) do {
						_indentval = _indent + __DUMP_INDENT__;
						_hash_key = _hash_indexer select _i;
						diag_log Format["%1 [%2]",_indent, _hash_key];
						_index = _hash_indexer find _hash_key;
						if ((_index > 0) && (_index < count _hash_values)) then {
							_value = _hash_values select _index;
							if (typeName _value == "ARRAY") then {
								[_value,_indentval] call XfArrayDump;
							} else {
								diag_log Format["%1 %2",_indentval,_value];
							};
						} else {
							diag_log Format["%1 %2",_indentval,"(Out of bounds)"];
						};
					};
				};				
				if ((isNil "_result") && !(IsNil "_default")) then { _result = _default };
			};
		};
	};
};

/// @FUNCTION
/// @NAME XfArrayDump
/// @DESC Dumps the content of an array to the Log file wit hthe given indent (or with the default indent if none given)
/// @ACCESS Server
/// @MP False
/// @PARAM Array The array
/// @PARAM String The indent to add to the dump (used mostly to align the dump with a preceding log. @DEFAULT ""
/// @RETURN Nothing
/// @REQUIRE Nothing
/// @REMARK
/// @EXAMPLE [_array, "   "] call XfArrayDump
//converted
XfArrayDump = {
	private ["_array","_indent","_indentval","_value","_j"];
	_array = _this select 0;
	_indent = _this select 1;
	diag_log Format["%1 %2",_indent,"ARRAY ["];
	_indentval = _indent + __DUMP_INDENT__;
	if (count _array > 0) then {
		{
			if (typeName _x == "ARRAY") then {
				[_x,_indentval] call XfArrayDump;
			} else {
				diag_log Format["%1 %2",_indentval,_x];
			};
		}foreach _array;
	};
	diag_log Format["%1 %2",_indent,"]"];
};

// An array which can be filtered with several conditions. Add a condition array per condition as parameter.
// Example: [_array,[_col,_compare,_value],[_col,_compare,_value]] call XfTableFilter;
//converted
XfTableFilter = {
	private ["_array","_result","_filters","_filter_column","_filter_value","_filter_compare","_value","_match"];
	_result = [];
	if (count _this <= 1) then {
		diag_log Format["(%1)[%2]: %3 %4",diag_tickTime,"hash_array.sqf","Not enough parameters",_this];
	} else {
		_array = _this select 0;
		_filters = [];
		// No need to check if there are no lines
		if (count _array > 0) then {
			_max_column = 0;
			For [{_i = 1}, {_i < count _this}, {_i = _i + 1}] do {
				if (count (_this select _i) >= 3) then {
					// We add the filter to the array plus we set the filter operator right.
					_filter_compare = toUpper((_this select _i) select 1);
					if !(_filter_compare in ["==","!=",">","<",">=","<=","U==","U!=","IN","!IN"]) then {
						diag_log Format["(%1)[%2]: %3 %4",diag_tickTime,"hash_array.sqf","Unknown comparison",_filter_compare];
						_filter_compare = "==";
					};
					(_this select _i) set [1,_filter_compare];
					_filter_column = (_this select _i) select 0;
					if (_filter_column > _max_column) then {_max_column = _filter_column};
					_filters set [ count _filters, (_this select _i)];
				};
			};
			// We loop through all lines
			{
				// We skip all lines which don't have enough columns
				_line = _x;
				if (typeName _line == "ARRAY") then {
					if (count _line > _max_column) then {
						_match = false;
						try {
							{
								_filter_column = _x select 0;
								_filter_compare = _x select 1;
								_filter_value = _x select 2;
								_value = _line select _filter_column;
								_match = switch (_filter_compare) do {
									case "!=": { (_value != _filter_value) };
									case ">": { (_value > _filter_value) };
									case "<": { (_value < _filter_value) };
									case ">=": { (_value >= _filter_value) };
									case "<=": { (_value <= _filter_value) };
									case "IN": { (_value in _filter_value) };
									case "!IN": { !(_value in _filter_value) };
									case "U==": { (toUpper(_value) == toUpper(_filter_value)) };
									case "U!=": { (toUpper(_value) != toUpper(_filter_value)) };
									default { (_value == _filter_value) };
								};
								if (!_match) exitWith{};
							} foreach _filters;
						} catch {
							_match = false;
						};
						if (_match) then {_result set[count _result, _x];};
					};		
				};
			} foreach _array;
		};
	};
	_result;
};